package han.chensing.latos.common.level.util;

import han.chensing.latos.common.level.content.objects.CommonLine;
import han.chensing.latos.common.level.content.timeLine.TimeLineEventSet;
import han.chensing.latos.common.level.content.timeLine.TimeLineRow;
import han.chensing.latos.common.level.content.timeLine.events.TimeLineEvent;
import han.chensing.latos.common.level.content.timeLine.events.TimeLineMotionEvent;
import han.chensing.latos.common.level.content.timeLine.frame.*;
import han.chensing.latos.common.level.function.ListReference;
import han.chensing.latos.common.level.function.TimeObject;

import java.util.List;

public class FrameUtil {
    public static FrameGeneral getGeneralLineNowFrame(CommonLine line, float time){
        return getGeneralLineNowFrame(line.getTimeLineRow(), time);
    }

    public static FrameGeneral getGeneralLineNowFrame(TimeLineRow row, float time){
        FrameGeneral aspectFrame1 = getGeneralAspectFrame(row,time, GeneralEventTypes.X);
        FrameGeneral aspectFrame2 = getGeneralAspectFrame(row,time, GeneralEventTypes.Y);
        FrameGeneral aspectFrame3 = getGeneralAspectFrame(row,time, GeneralEventTypes.Rotate);
        FrameGeneral aspectFrame4 = getGeneralAspectFrame(row,time, GeneralEventTypes.Alpha);
        FrameGeneral aspectFrame5 = getGeneralAspectFrame(row,time, GeneralEventTypes.Speed);
        FrameGeneral frame = FrameGeneral.overlappingFrames(aspectFrame1, aspectFrame2, aspectFrame3, aspectFrame4, aspectFrame5);
        frame.setTime(time);
        return frame;
    }

    public static FrameGeneral getGeneralAspectFrame(TimeLineEventSet<TimeLineMotionEvent,FrameGeneral> timeLineEventSet, float time){
        FrameGeneral lastKeyFrame = getLastDrawKeyFrame(time, timeLineEventSet);
        lastKeyFrame=lastKeyFrame==null?new FrameGeneral():lastKeyFrame;
        FrameGeneral outFrame=new FrameGeneral(lastKeyFrame);
        outFrame.addTypes((GeneralEventTypes) timeLineEventSet.getType());
        TimeLineMotionEvent nowLineEvent = getNowLineEvent(time, timeLineEventSet);
        if (nowLineEvent!=null){
            float fromTime = nowLineEvent.getFromTime();
            float toTime = nowLineEvent.getToTime();
            float duration = toTime - fromTime;
            float x = (time - fromTime) / duration;
            FrameGeneral animationFrame = nowLineEvent.getFrame(x);
            animationFrame.addTypes((GeneralEventTypes) timeLineEventSet.getType());
            return animationFrame;
        }
        return outFrame;
    }

    @SuppressWarnings("unchecked")
    public static FrameGeneral getGeneralAspectFrame(TimeLineRow timeLineRow, float time, GeneralEventTypes type){
        TimeLineEventSet<TimeLineMotionEvent,FrameGeneral> timeLineEventSet = (TimeLineEventSet<TimeLineMotionEvent, FrameGeneral>) getTimeLineEventSetByType(timeLineRow, type);
        if (timeLineEventSet==null)
            return null;
        return getGeneralAspectFrame(timeLineEventSet,time);
    }

    public static FrameEditor getEditorAspectFrame(TimeLineEventSet<?,FrameEditor> timeLineEventSet, float time){
        FrameEditor lastKeyFrame = getLastDrawKeyFrame(time, timeLineEventSet);
        lastKeyFrame=lastKeyFrame==null?new FrameEditor():lastKeyFrame;
        FrameEditor outFrame=new FrameEditor(lastKeyFrame);
        outFrame.addTypes((EditorEventTypes) timeLineEventSet.getType());
        return outFrame;
    }

    @SuppressWarnings("unchecked")
    public static FrameEditor getEditorAspectFrame(TimeLineRow timeLineRow, float time, EditorEventTypes editorEventTypes){
        TimeLineEventSet<?,FrameEditor> timeLineEventSet = (TimeLineEventSet<?, FrameEditor>) getTimeLineEventSetByType(timeLineRow, editorEventTypes);
        if (timeLineEventSet==null)
            return null;
        return getEditorAspectFrame(timeLineEventSet,time);
    }

    public static FrameGeneral getGeneralAspectFrame(CommonLine commonLine, float time, GeneralEventTypes type){
        return getGeneralAspectFrame(commonLine.getTimeLineRow(),time,type);
    }

    public static <T extends TimeLineEvent<F>,F extends BaseFrame<?>> T getNowLineEvent(float time, TimeLineEventSet<T,F> timeLineEventSet){
        List<T> timeLineEvents = timeLineEventSet.getTimeLineEvents();
        for (T timeLineEvent : timeLineEvents) {
            float fromTime = timeLineEvent.getFromTime();
            float toTime = timeLineEvent.getToTime();
            if (fromTime <= time && time <= toTime)
                return timeLineEvent;
        }
        return null;
    }

    public static TimeLineEventSet<?,?> getTimeLineEventSetByType(TimeLineRow row, Object type){
        for (TimeLineEventSet<?,?> timeLineEventSet : row.getTimeLineEventSets()) {
            if (timeLineEventSet.getType()==type)
                return timeLineEventSet;
        }
        return null;
    }

    public static <F extends BaseFrame<?>> F getLastDrawKeyFrame(float time, TimeLineEventSet<?,F> timeLineEventSet){
        int timeLineEventIndex = TimeUtil.getLastHasTimeIndex(new ListReference<>() {
            @Override
            public TimeObject get(int index) {
                return () -> timeLineEventSet.getTimeLineEvents().get(index).getFromTime();
            }
            @Override
            public int size() {
                return timeLineEventSet.getTimeLineEvents().size();
            }
        }, time);
        F lastTimeFrame = TimeUtil.getLastHasTime(timeLineEventSet.getKeyFrames(), time);
        TimeLineEvent<F> lastTimeLineEvent =timeLineEventIndex==-1?
                null:timeLineEventSet.getTimeLineEvents().get(timeLineEventIndex);
        float frameTime=lastTimeFrame==null?-Float.MAX_VALUE:lastTimeFrame.getTime();
        float eventTime=lastTimeLineEvent==null?-Float.MAX_VALUE:lastTimeLineEvent.getToTime();
        if (frameTime<eventTime){
            assert lastTimeLineEvent != null;
            float duration = lastTimeLineEvent.getToTime() - lastTimeLineEvent.getFromTime();
            return lastTimeLineEvent.getFrame(duration);
        }
        return lastTimeFrame;
    }

    public static <F extends BaseFrame<?>> F getLastKeyFrame(float time, TimeLineEventSet<?,F> timeLineEventSet){
        return TimeUtil.getLastHasTime(timeLineEventSet.getKeyFrames(), time);
    }

    public static <F extends BaseFrame<?>> F getNextKeyFrame(float time, TimeLineEventSet<?,F> timeLineEventSet) {
        return TimeUtil.getNextHasTime(timeLineEventSet.getKeyFrames(), time);
    }

    public static <F extends BaseFrame<?>> F getNearestKeyFrame(float time, TimeLineEventSet<?,F> timeLineEventSet){
        return TimeUtil.getNearestHasTime(timeLineEventSet.getKeyFrames(),time);
    }

    public static FrameGeneral[] getKeyFramesBothSidesByTime(List<FrameGeneral> frames, float time){
        int size = frames.size();
        if (size <=1)return null;
        for (int i = size - 2; i >= 0; i--) {
            FrameGeneral nowFrame = frames.get(i);
            float nowFrameTime = nowFrame.getTime();
            if (time>=nowFrameTime){
                if (i== size)return null;
                return new FrameGeneral[]{nowFrame,frames.get(i+1)};
            }
        }
        return null;
    }

    public static <F extends BaseFrame<?>> TimeLineEvent<F> getNowTimeLineEvent(TimeLineEventSet<?,F> timeLineEventSet,float time){
        List<? extends TimeLineEvent<F>> timeLineEvents = timeLineEventSet.getTimeLineEvents();
        for (TimeLineEvent<F> timeLineEvent : timeLineEvents) {
            float from = timeLineEvent.getFromTime();
            float to = timeLineEvent.getToTime();
            if (MathUtil.isInRange(from,time,to))
                return timeLineEvent;
        }
        return null;
    }
}
